package com.onlyxiahui.aware.basic.extend.javassist.dynamicclass;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javassist.CannotCompileException;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.AnnotationMemberValue;
import javassist.bytecode.annotation.BooleanMemberValue;
import javassist.bytecode.annotation.ByteMemberValue;
import javassist.bytecode.annotation.CharMemberValue;
import javassist.bytecode.annotation.ClassMemberValue;
import javassist.bytecode.annotation.DoubleMemberValue;
import javassist.bytecode.annotation.EnumMemberValue;
import javassist.bytecode.annotation.FloatMemberValue;
import javassist.bytecode.annotation.IntegerMemberValue;
import javassist.bytecode.annotation.LongMemberValue;
import javassist.bytecode.annotation.MemberValue;
import javassist.bytecode.annotation.ShortMemberValue;
import javassist.bytecode.annotation.StringMemberValue;

/**
 * 
 * Description <br>
 * Date 2020-04-10 15:37:38<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public class ClassBuilderUtil {

	static Logger logger = LoggerFactory.getLogger(ClassBuilderUtil.class);

//	public static final String basePackage = "com.onlyxiahui.bean.";

	static final ClassPool classPool = ClassPool.getDefault();

	/**
	 * 根据原始泛型类创建一个新的Class
	 * 
	 * @param originalGenericType 原始泛型类
	 * @param className           新类的名称
	 * @param properties          动态字段
	 * @return 动态生成类
	 */
	public static DynamicClass createDynamicByOriginalGenericClass(Class<?> originalGenericType, String className, List<Property> properties) {
		DynamicClass dynamicClass = new DynamicClass();
		try {
			if (originalGenericType != null) {
				try {
					CtClass tmp = classPool.getCtClass(className);
					if (tmp != null) {
						tmp.detach();
					}
				} catch (NotFoundException e) {
				}
				CtClass ctClass = classPool.makeClass(className);
				Field[] originalFields = originalGenericType.getDeclaredFields();
				// String genericFieldName=dynamicResponseParameters.genericFieldName();
				String genericFieldName = "";
				// String fieldClassName = name + genericFieldName;
				for (Field field : originalFields) {
					if (field.getName().equals(genericFieldName)) {
						// ctClass.addField(createByDynamicField(field,fieldClassName,dynamicResponseParameters,ctClass,dynamicClass));
					} else {
						ctClass.addField(createByOriginalField(field, ctClass));
					}
				}
				dynamicClass.setTargetClass(ctClass.toClass());
				// return ctClass.toClass();
			}
		} catch (Throwable e) {
			logger.error(e.getMessage(), e);
		}
		return dynamicClass;
	}

	public static CtField createByDynamicField(Field field, String fieldClassName, ClassData classData, CtClass ctClass, DynamicClass dynamicClass) throws CannotCompileException {
		Class<?> fieldClazz = createClass(fieldClassName, classData.getProperties());
		dynamicClass.setFieldClass(fieldClazz);
		CtField ctField = new CtField(getFieldType(fieldClazz), field.getName(), ctClass);
		ctField.setModifiers(Modifier.PUBLIC);
		addAnnotation(ctField, field, ctClass);
		return ctField;
	}

	private static CtField createByOriginalField(Field field, CtClass ctClass) throws CannotCompileException {
		CtField ctField = new CtField(getFieldType(field.getType()), field.getName(), ctClass);
		ctField.setModifiers(Modifier.PUBLIC);
		// 此处需要判断原field是否包含注解
		addAnnotation(ctField, field, ctClass);
		return ctField;
	}

	/**
	 * createModel
	 * 
	 * @param name         类名
	 * @param propertyList 属性集合
	 * @return 动态生成类
	 */
	public static Class<?> createClass(String className, List<Property> propertyList) {
		try {
			CtClass tmp = classPool.getCtClass(className);
			if (tmp != null) {
				tmp.detach();
			}
		} catch (NotFoundException e) {
		}
		CtClass ctClass = classPool.makeClass(className);
		try {
			int fieldCount = 0;
			for (Property property : propertyList) {
				// field名称不能为空,导致非空异常
				if (property.getName() != null && !"".equals(property.getName()) && !"null".equals(property.getName())) {
					ctClass.addField(createField(property, ctClass));
					fieldCount++;
				}
			}
			if (fieldCount > 0) {
				return ctClass.toClass();
			}
		} catch (Throwable e) {
			logger.error(e.getMessage());
		}
		return null;
	}

	public static Class<?> createClass(ClassData classData) {
		Class<?> clazz = null;
		if (null != classData) {
			String className = classData.getClassName();

			if (null != className) {
				try {
					CtClass tmp = classPool.getCtClass(className);
					if (tmp != null) {
						tmp.detach();
					}
				} catch (NotFoundException e) {
				}
				CtClass ctClass = classPool.makeClass(className);
				try {
					List<Property> properties = classData.getProperties();

					if (null != properties) {
						// int fieldCount = 0;
						for (Property property : properties) {
							// field名称不能为空,导致非空异常
							if (property.getName() != null && !"".equals(property.getName()) && !"null".equals(property.getName())) {
								ctClass.addField(createField(property, ctClass));
								// fieldCount++;
							}
						}

						ConstPool constPool = ctClass.getClassFile().getConstPool();
						AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
						if (null != classData.getAnnotations()) {
							for (java.lang.annotation.Annotation ja : classData.getAnnotations()) {
								Annotation a = toAnnotation(ja, ctClass);
								attr.addAnnotation(a);
							}
						}
						if (null != classData.getAnnotationDatas()) {
							for (AnnotationData ad : classData.getAnnotationDatas()) {
								Annotation a = toAnnotation(ad, ctClass);
								attr.addAnnotation(a);
							}
						}

						ctClass.getClassFile().addAttribute(attr);
						// if (fieldCount > 0) {
						clazz = ctClass.toClass();
						// }
					}
				} catch (Throwable e) {
					logger.error(e.getMessage());
				}
			}
		}
		return clazz;
	}

	public static CtField createField(Property property, CtClass ctClass) throws NotFoundException, CannotCompileException {

		if (property.getDataClass() == Object.class && null != property.getNodes() && !property.getNodes().isEmpty()) {

			String name = property.getName();
			List<Property> propertyList = property.getNodes();
			Class<?> classType = createClass(name, propertyList);

			CtField field = new CtField(getFieldType(classType), name, ctClass);
			field.setModifiers(Modifier.PUBLIC);
			ConstPool constPool = ctClass.getClassFile().getConstPool();
			AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
			if (null != property.getAnnotations()) {
				for (java.lang.annotation.Annotation ja : property.getAnnotations()) {
					Annotation a = toAnnotation(ja, ctClass);
					attr.addAnnotation(a);
				}
			}
			if (null != property.getAnnotationDatas()) {
				for (AnnotationData ad : property.getAnnotationDatas()) {
					Annotation a = toAnnotation(ad, ctClass);
					attr.addAnnotation(a);
				}
			}
			field.getFieldInfo().addAttribute(attr);
			return field;
		} else {

			CtField field = new CtField(getFieldType(property.getDataClass()), property.getName(), ctClass);
			field.setModifiers(Modifier.PUBLIC);
			ConstPool constPool = ctClass.getClassFile().getConstPool();
			AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
			if (null != property.getAnnotations()) {
				for (java.lang.annotation.Annotation ja : property.getAnnotations()) {
					Annotation a = toAnnotation(ja, ctClass);
					attr.addAnnotation(a);
				}
			}
			if (null != property.getAnnotationDatas()) {
				for (AnnotationData ad : property.getAnnotationDatas()) {
					Annotation a = toAnnotation(ad, ctClass);
					attr.addAnnotation(a);
				}
			}
			field.getFieldInfo().addAttribute(attr);
			return field;
		}
	}

	public static CtClass getFieldType(Class<?> propetyType) {
		CtClass fieldType = null;
		try {
			if (!propetyType.isAssignableFrom(Void.class)) {
				fieldType = classPool.get(propetyType.getName());
			} else {
				fieldType = classPool.get(String.class.getName());
			}
		} catch (NotFoundException e) {
			// 抛异常
			ClassClassPath path = new ClassClassPath(propetyType);
			classPool.insertClassPath(path);
			try {
				fieldType = classPool.get(propetyType.getName());
			} catch (NotFoundException e1) {
				logger.error(e1.getMessage(), e1);
				// can't find
			}
		}
		return fieldType;
	}

	public static List<AnnotationPropertyData> getValue(java.lang.annotation.Annotation a) {
		List<Method> ms = new ArrayList<>();
		Method[] methodArray = java.lang.annotation.Annotation.class.getMethods();
		for (Method method : methodArray) {
			ms.add(method);
		}
		List<AnnotationPropertyData> list = new ArrayList<>();
		if (null != a) {
			Method[] methods = a.annotationType().getMethods();
			if (null != methods && methods.length > 0) {
				for (Method method : methods) {
					String name = method.getName();

					if (!ms.contains(method)) {
						try {
							Object value = method.invoke(a);

							AnnotationPropertyData p = new AnnotationPropertyData();
							p.setName(name);
							p.setValue(value);

							list.add(p);

						} catch (IllegalAccessException e) {
							e.printStackTrace();
						} catch (IllegalArgumentException e) {
							e.printStackTrace();
						} catch (InvocationTargetException e) {
							e.printStackTrace();
						}
					}
				}
			}
		}
		return list;
	}

	public static List<Annotation> getAnnotations(Field field, CtClass ctClass) {
		List<Annotation> list = new ArrayList<>();
		java.lang.annotation.Annotation[] javaAs = field.getAnnotations();
		if (null != javaAs && javaAs.length > 0) {
			for (java.lang.annotation.Annotation ja : javaAs) {
				Annotation a = toAnnotation(ja, ctClass);
				list.add(a);
			}
		}
		return list;
	}

	public static Annotation toAnnotation(java.lang.annotation.Annotation ja, CtClass ctClass) {
		Class<?> javaAc = ja.annotationType();
		String name = javaAc.getName();
		ConstPool constPool = ctClass.getClassFile().getConstPool();
		Annotation ann = new Annotation(name, constPool);

		List<AnnotationPropertyData> list = getValue(ja);

		for (AnnotationPropertyData pd : list) {
			String mn = pd.getName();
			Object v = pd.getValue();
			MemberValue mv = getMemberValue(ctClass, constPool, mn, v);
			ann.addMemberValue(mn, mv);
		}
		return ann;
	}

	public static Annotation toAnnotation(AnnotationData ad, CtClass ctClass) {
		String name = ad.getName();
		Annotation ann = null;
		if (null != name && !name.isEmpty()) {
			ConstPool constPool = ctClass.getClassFile().getConstPool();
			ann = new Annotation(name, constPool);
			List<AnnotationPropertyData> list = ad.getProperties();
			if (null != list) {
				for (AnnotationPropertyData pd : list) {
					String mn = pd.getName();
					Object v = pd.getValue();
					MemberValue mv = getMemberValue(ctClass, constPool, mn, v);
					ann.addMemberValue(mn, mv);
				}
			}
		}
		return ann;
	}

	private static MemberValue getMemberValue(CtClass ctClass, ConstPool constPool, String mn, Object v) {
		MemberValue mv = null;
		if (v instanceof String) {
			mv = new StringMemberValue(v.toString(), constPool);
		} else if (v instanceof Integer) {
			mv = new IntegerMemberValue((Integer) v, constPool);
		} else if (v instanceof Long) {
			mv = new LongMemberValue((Long) v, constPool);
		} else if (v instanceof Float) {
			mv = new FloatMemberValue((Float) v, constPool);
		} else if (v instanceof Double) {
			mv = new DoubleMemberValue((Double) v, constPool);
		} else if (v instanceof Character) {
			mv = new CharMemberValue((Character) v, constPool);
		} else if (v instanceof Byte) {
			mv = new ByteMemberValue((Byte) v, constPool);
		} else if (v instanceof Short) {
			mv = new ShortMemberValue((Short) v, constPool);
		} else if (v instanceof Boolean) {
			mv = new BooleanMemberValue((Boolean) v, constPool);
		} else if (v instanceof Class) {
			Class<?> c = (Class<?>) v;
			mv = new ClassMemberValue(c.getName(), constPool);
		} else if (v.getClass().isEnum()) {
			EnumMemberValue emv = new EnumMemberValue(constPool);
			emv.setType(v.getClass().getName());
			mv = emv;
		} else if (v.getClass().isArray()) {
//			Class<?> propertyClass = v.getClass();
//			Class<?> genericClass = propertyClass.getComponentType();
			// TODO
		} else if (v instanceof java.lang.annotation.Annotation) {
			Annotation a = toAnnotation((java.lang.annotation.Annotation) v, ctClass);
			AnnotationMemberValue am = new AnnotationMemberValue(a, constPool);
			mv = am;
		}
		return mv;
	}

	public static void addAnnotation(CtField target, Field source, CtClass ctClass) {
		List<Annotation> as = getAnnotations(source, ctClass);
		if (null != as && !as.isEmpty()) {
			ConstPool constPool = ctClass.getClassFile().getConstPool();
			AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
			for (Annotation a : as) {
				attr.addAnnotation(a);
			}
			target.getFieldInfo().addAttribute(attr);
		}
	}
}
